//  Copyright (c) 2008  Yoong-ki Ahn & Youngjin Kim < mexif@googlegroups.com>
//  See the enclosed file COPYING for license information (MIT Licence).
//  If you did not receive this file,
//  see http://www.opensource.org/licenses/mit-license.php.

#include "stdafx.h"
#include "fill_exif.h"
#include "mexiflib.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

void FormatTagName(        CString& str, mexif_tag_t* tag );
void FormatTagValue(       CString& str, mexif_tag_t* tag );
void FormatTagDescription( CString& str, mexif_tag_t* tag );
bool FormatTagValue_DateTime( CString& str, mexif_tag_t* tag );
void FormatTagNumber(      CString& str, mexif_tag_t* tag, UINT maxCount );


void fill_exif( CPropTree* tree, LPCTSTR filename )
{
	tree->DeleteAllItems();

	CByteArray	content;
	CFile		file;

	if ( !file.Open( filename, CFile::modeRead ) )
	{
		return;
	}

	fill_fileinfo( tree, file );

	content.SetSize( file.GetLength() );
	file.Read( content.GetData(), content.GetCount() );

	marker_array	apps;

	if ( !fill_jpeginfo( tree, content, apps ) )
		return;

	// print out known apps
	marker_array::iterator a = apps.begin();
	marker_array::iterator b = apps.end();

	for ( ; a!=b; ++a )
	{
		const unsigned char* p = a->data;
		unsigned int s = a->len;

		if ( a->marker == MEXIF_JPEG_MARKER_APP0 )
		{
			if ( mexif_is_jfif_marker_prefixed( p, s ) )
			{
				fill_jfifinfo( tree, p, s );
			}
		}
		else if ( a->marker == MEXIF_JPEG_MARKER_APP1 )
		{
			if ( mexif_is_exif_marker_prefixed( p, s ) )
				fill_exifinfo( tree, p, s );
		}
	}
}

void fill_fileinfo( CPropTree* tree, CFile& file )
{
	ULONGLONG fileSize = file.GetLength();

	// Create a root item (root items should always be CPropTreeItem object since they
	// can not have properties
	CPropTreeItem* pRoot;
	CPropTreeItem* pItem;
	CString text;

	//	set general information.
	pRoot = tree->InsertItem(new CPropTreeItem());
	pRoot->SetLabelText(_T("File Info"));
	pRoot->SetInfoText(_T("General informations"));

	pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);
	pItem->SetLabelText(_T("File Size"));
	pItem->SetInfoText(_T("file size in bytes."));
	text.Format( TEXT("%I64u"), fileSize );
	pItem->SetItemValue((LPARAM) text.GetString() );

	text = file.GetFilePath();
	{
		int a = text.ReverseFind( '/' );
		int b = text.ReverseFind( '\\' );
		if ( a < b )
			a = b;
		if ( a >= 0 )
			text.Truncate( a+1 );
	}
	pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);
	pItem->SetLabelText(_T("File Path"));
	pItem->SetInfoText(_T("file path."));
	pItem->SetItemValue((LPARAM) text.GetString() );

	pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);
	pItem->SetLabelText(_T("File Name"));
	pItem->SetInfoText(_T("file name."));
	pItem->SetItemValue((LPARAM) file.GetFileName().GetString() );

	CFileStatus status;
	file.GetStatus( status );

	text = status.m_ctime.Format( TEXT("%Y. %m. %d. %A  %H:%M:%S") );
	pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);
	pItem->SetLabelText(_T("Created time"));
	pItem->SetInfoText(_T("When file is created."));
	pItem->SetItemValue((LPARAM) text.GetString() );

	text = status.m_mtime.Format( TEXT("%Y. %m. %d. %A  %H:%M:%S") );
	pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);
	pItem->SetLabelText(_T("Modified time"));
	pItem->SetInfoText(_T("When file is modified."));
	pItem->SetItemValue((LPARAM) text.GetString() );
}

bool fill_jpeginfo( CPropTree* tree, CByteArray& content, marker_array& apps )
{
	mexif_jpeg_t	jpeg;
	if ( MEXIF_RET_OK != mexif_jpeg_init( &jpeg, content.GetData(), content.GetSize() ) )
		return false;

	CPropTreeItem* pRoot;
	CPropTreeItem* pItem;
	CString text;

	pRoot = tree->InsertItem(new CPropTreeItem());
	pRoot->SetLabelText(_T("Jpeg Info"));
	pRoot->SetInfoText(_T("Jpeg structure information"));

	{
		CPropTreeItemImage::Value val;
		val.pb  = content.GetData();
		val.len = content.GetSize();

		pItem = tree->InsertItem( new CPropTreeItemImage(), pRoot );
		pItem->SetLabelText( TEXT("Image") );
		pItem->SetItemValue( (LPARAM) &val );
	}

	// Place string name of chunk on left side
	// App shows length, and then about 16 bytes length array of string
	// Comment should be shown as string

	int i = 0;
	for ( ; mexif_jpeg_isvalid( &jpeg ); mexif_jpeg_next( &jpeg ), ++i )
	{
		int  marker = mexif_jpeg_getmarker( &jpeg );
		UINT len    = mexif_jpeg_getlength( &jpeg );
		UINT offset = mexif_jpeg_getoffset( &jpeg );
		const unsigned char* chunk = (const unsigned char*) mexif_jpeg_getchunk( &jpeg );
		LPCTSTR markername = mexif_jpeg_getmarkername( marker );
		LPCTSTR headername = 0;

		// Register app marker
		if ( MEXIF_JPEG_MARKER_APP0 <= marker && marker <= MEXIF_JPEG_MARKER_APP15 )
		{
			marker_t	m;
			m.marker = marker;
			m.len    = len - 2;
			m.data   = chunk + 2;
			apps.push_back( m );

			//	check if known header.
			if      ( MEXIF_JPEG_MARKER_APP0 == marker && mexif_is_jfif_marker_prefixed( m.data, m.len ) )
				headername = TEXT("JFIF");
			else if ( MEXIF_JPEG_MARKER_APP1 == marker && mexif_is_exif_marker_prefixed( m.data, m.len ) )
				headername = TEXT("Exif");
		}

		pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);
		if ( headername && markername )
			text.Format( TEXT("%s:%s"), markername, headername );
		else if ( markername )
			text.Format( TEXT("%s"), markername );
		else if ( marker == MEXIF_JPEG_MARKER_PseudoImage )
			text = TEXT("Image data");
		else
			text.Format( TEXT("chunk %d"), i+1 );
		pItem->SetLabelText( text );

		text.Format( TEXT("marker:%x"), marker );
		pItem->SetInfoText( text );

		text.Format( TEXT("offset:0x%x, length:%d"), offset, len );
		pItem->SetItemValue((LPARAM) text.GetString() );
	}

	mexif_jpeg_close( &jpeg );
	return true;
}


void fill_jfifinfo( CPropTree* tree, const unsigned char* p, UINT len )
{
	CPropTreeItem* pRoot;
	CPropTreeItem* pItem;
	CString text;

	pRoot = tree->InsertItem( new CPropTreeItem() );
	pRoot->SetLabelText( L"JFIF" );
	pRoot->SetLabelText( L"JFIF header" );

	mexif_jfif_partial_t jfif;

	if ( MEXIF_RET_OK == mexif_jpeg_fill_jfif( &jfif, p, len ) )
	{
		pItem = tree->InsertItem( new CPropTreeItemStatic, pRoot );
		pItem->SetLabelText( TEXT("header") );
		text.Empty();
		// ASCII -> UNICODE.
		for ( UINT i=0; i<5; ++i )
			text.AppendChar( jfif.Identifier[i] );
		pItem->SetItemValue( (LPARAM) text.GetString() );

		pItem = tree->InsertItem( new CPropTreeItemStatic, pRoot );
		pItem->SetLabelText( TEXT("version") );
		text.Format( TEXT("%u.%02u"), jfif.VersionMajor, jfif.VersionMinor );
		pItem->SetItemValue( (LPARAM) text.GetString() );

		pItem = tree->InsertItem( new CPropTreeItemStatic, pRoot );
		pItem->SetLabelText( TEXT("units") );
		if ( jfif.Units == 0 )
			text = TEXT("aspect pixel ratio");
		else if ( jfif.Units == 1 )
			text = TEXT("dots per inch");
		else if ( jfif.Units == 2 )
			text = TEXT("dots per cm");
		else
			text.Format( TEXT("unknown unit:%u"), jfif.Units );
		pItem->SetItemValue( (LPARAM) text.GetString() );

		pItem = tree->InsertItem( new CPropTreeItemStatic, pRoot );
		pItem->SetLabelText( TEXT("density") );
		text.Format( TEXT("%u x %u"), jfif.Xdensity, jfif.Ydensity );
		pItem->SetItemValue( (LPARAM) text.GetString() );

		pItem = tree->InsertItem( new CPropTreeItemStatic, pRoot );
		pItem->SetLabelText( TEXT("thumbnail") );
		text.Format( TEXT("%u x %u"), jfif.Xthumbnail, jfif.Ythumbnail );
		pItem->SetItemValue( (LPARAM) text.GetString() );
	}
}

void fill_exifinfo( CPropTree* tree, const unsigned char* pexif, UINT exifSize )
{
	mexif_exif_t exif;
	if ( MEXIF_RET_OK != mexif_exif_init( &exif, pexif, exifSize ) )
		return;

	fill_exifinfo_sec( tree, &exif, MEXIF_SECTION_0 );
	fill_exifinfo_sec( tree, &exif, MEXIF_SECTION_1 );
	fill_exifinfo_sec( tree, &exif, MEXIF_SECTION_EXIF );
	fill_exifinfo_sec( tree, &exif, MEXIF_SECTION_GPS );
	fill_exifinfo_sec( tree, &exif, MEXIF_SECTION_INTEROP );
	fill_exifinfo_sec( tree, &exif, MEXIF_SECTION_MAKERNOTE );

	mexif_exif_close( &exif );
}


void fill_exifinfo_sec( CPropTree* tree, void* exif, int sectionnum )
{
	CPropTreeItem* pRoot;
	CPropTreeItem* pItem;
	CString text;

	mexif_section_t section;

	if ( MEXIF_RET_OK != mexif_section_select( &section, (mexif_exif_t*) exif, (mexif_section_type_t) sectionnum ) )
		return;

	pRoot = tree->InsertItem(new CPropTreeItem());

	text.Format( TEXT("Exif: %s"), mexif_get_section_type_str( sectionnum ) );
	pRoot->SetLabelText( text );

	text.Format( TEXT("Total %d tags in this section."), section.count );
	pRoot->SetInfoText( text );

	if ( MEXIF_SECTION_0 == sectionnum )
	{
		pRoot->Expand();
	}

	mexif_tag_t tag;

	//	if IFD_1, show thumbnail.
	if ( sectionnum == MEXIF_SECTION_1 )
	{
		UINT offset = 0;
		UINT length = 0;

		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_JPEGInterchangeFormat ) )
			offset = mexif_tag_value_uint( &tag );

		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_JPEGInterchangeFormatLength ) )
			length = mexif_tag_value_uint( &tag );

		if ( length > 0  && offset > 0 && (offset+length) <= section.ifd.size )
		{
			CPropTreeItemImage::Value val;
			val.pb = section.ifd.data + offset;
			val.len = length;

			pItem = tree->InsertItem( new CPropTreeItemImage(), pRoot );
			pItem->SetLabelText( TEXT("Thumbnail") );
			pItem->SetItemValue( (LPARAM) &val );
		}
	}

	// Never seen tags more than 200.
	unsigned int total_tags = mexif_count_tags( &section );
	if ( total_tags > 200 )
	{
		pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);

		pItem->SetLabelText( TEXT("error") );
		pItem->SetInfoText( TEXT("tag count is too many.") );
		text.Format( TEXT("count = %u."), total_tags );
		pItem->SetItemValue((LPARAM) text.GetString() );
	}
	else
	//	other tags.
	for ( unsigned int i=0; i< total_tags; ++i  )
	{
		pItem = tree->InsertItem(new CPropTreeItemStatic(), pRoot);

		if ( MEXIF_RET_OK != mexif_select_tag( &section, &tag, i ) )
		{
			text.Format( TEXT("%d th tag"), i+1 );
			pItem->SetLabelText( text );
			pItem->SetInfoText( text );
			text.Format( TEXT("load error.") );
			pItem->SetItemValue((LPARAM) text.GetString() );

			continue;
		}
		else
		{
			FormatTagName( text, &tag );
			pItem->SetLabelText( text );

			FormatTagDescription( text, &tag );
			pItem->SetInfoText( text );

			// Where should I print out each tag category?
// 			bool formated = false;
// 			if ( sectionnum == MEXIF_SECTION_0 )
// 			{
// 				if ( tag.tag_id == MEXIF_TAGID_EXIF_DateTime )
// 				{
// 					formated = true;
// 				}
// 			}
// 
// 			if ( !formated )
				FormatTagValue( text, &tag );
			pItem->SetItemValue((LPARAM) text.GetString() );

			if ( !tag.value_format )
				continue;
		}

		mexif_section_t subsec;
		mexif_subsection_load( &subsec, &tag );
		for ( unsigned int ii = 0; ii<mexif_count_tags( &subsec ); ++ii )
		{
			CPropTreeItem* pItem2;
			pItem2 = tree->InsertItem(new CPropTreeItemStatic(), pItem );

			if ( MEXIF_RET_OK != mexif_select_tag( &subsec, &tag, ii ) )
			{
				text.Format( TEXT("%d th tag"), ii+1 );
				pItem2->SetLabelText( text );
				pItem2->SetInfoText( text );
				text.Format( TEXT("load error.") );
				pItem2->SetItemValue((LPARAM) text.GetString() );
				continue;
			}
			else
			{
				FormatTagName( text, &tag );
				pItem2->SetLabelText( text );

				FormatTagDescription( text, &tag );
				pItem2->SetInfoText( text );

				FormatTagValue( text, &tag );
				pItem2->SetItemValue((LPARAM) text.GetString() );
			}
		}
		mexif_section_close( &subsec );
	}

	mexif_section_close( &section );
}

void FormatTagName( CString& text, mexif_tag_t* tag )
{
	if ( tag->tag_db_entry && tag->tag_db_entry->name )
		text = tag->tag_db_entry->name;
	else
		text.Format( TEXT("(tag 0x%04x)"), tag->tag_id );
}

void FormatTagDescription( CString& str, mexif_tag_t* tag )
{
	str.Empty();

	if ( tag->tag_db_entry && tag->tag_db_entry->desc )
	{
		str = tag->tag_db_entry->desc ;
		str.AppendChar( '\n' );
	}
	str.AppendFormat( TEXT("id:0x%04x (%d), "), tag->tag_id, tag->tag_id );

	switch( tag->value_type )
	{
	case MEXIF_VALUE_ASCII   :		str.AppendFormat( TEXT("string, length=%d"), tag->value_size );	return;
	case MEXIF_VALUE_UNDEFINED  :	str.AppendFormat( TEXT("binary, length=%d"), tag->value_size );	return;
	case MEXIF_VALUE_BYTE   :		str.Append( TEXT("byte") );		break;
	case MEXIF_VALUE_UINT16 :		str.Append( TEXT("uint16") );	break;
	case MEXIF_VALUE_UINT32 :		str.Append( TEXT("uint32") );	break;
	case MEXIF_VALUE_SINT8  :		str.Append( TEXT("char") );		break;
	case MEXIF_VALUE_SINT16 :		str.Append( TEXT("int16") );	break;
	case MEXIF_VALUE_SINT32 :		str.Append( TEXT("int32") );	break;
	case MEXIF_VALUE_RATIONAL :		str.Append( TEXT("float") );	break;
	case MEXIF_VALUE_SRATIONAL :	str.Append( TEXT("float") );	break;
	default : str.AppendFormat( TEXT("unknown-%d"), tag->value_type ); break;
	}

	if ( tag->value_count > 1 )
		str.AppendFormat( TEXT("[%d]"), tag->value_count );
}

void FormatTagValue( CString& str, mexif_tag_t* tag )
{
	str.Empty();

	//	specific tag formatting.
	if ( tag->ifd.section == MEXIF_SECTION_0 )
	{
		if ( tag->tag_id == MEXIF_TAGID_EXIF_DateTime )
		{
			if ( FormatTagValue_DateTime( str, tag ) )
				return;
		}
	}
	else if ( tag->ifd.section == MEXIF_SECTION_EXIF )
	{
		if ( tag->tag_id == MEXIF_TAGID_EXIF_DateTimeOriginal || tag->tag_id == MEXIF_TAGID_EXIF_DateTimeDigitized )
		{
			if ( FormatTagValue_DateTime( str, tag ) )
				return;
		}
	}

	//	general formatting.
	switch( tag->value_type )
	{
	case MEXIF_VALUE_BYTE   :
	case MEXIF_VALUE_UINT16 :
	case MEXIF_VALUE_UINT32 :
	case MEXIF_VALUE_SINT8  :
	case MEXIF_VALUE_SINT16 :
	case MEXIF_VALUE_SINT32 :
	case MEXIF_VALUE_RATIONAL :
	case MEXIF_VALUE_SRATIONAL :
		FormatTagNumber( str, tag, 4 );
		break;

	case MEXIF_VALUE_UNDEFINED :
		FormatTagNumber( str, tag, 16 );
		break;

	case MEXIF_VALUE_ASCII :
		//	change to unicode.
		str.AppendChar( '"' );
		for ( LPCSTR p = (LPCSTR) tag->value_pointer; *p; ++p )
		{
			switch( *p )
			{
			case 0 :		str.Append( TEXT("\\0") );	break;
			case '\r' :		str.Append( TEXT("\\r") );	break;
			case '\n' :		str.Append( TEXT("\\n") );	break;
			case '\t' :		str.Append( TEXT("\\t") );	break;
			case '\\' :		str.Append( TEXT("\\\\") );	break;
			case '\"' :		str.Append( TEXT("\\\"") );	break;
			default :
				str.AppendChar( *p );
			}
		}
		str.AppendChar( '"' );
		break;
	}
}

void FormatTagNumber( CString& str, mexif_tag_t* tag, UINT maxCount )
{
	maxCount = tag->value_count < maxCount ? tag->value_count : maxCount;

	if ( maxCount > 1 )
		str.Append( TEXT("{ ") );

	UINT i = 0;
	for ( ; i<maxCount; ++i )
	{
		UINT val;
		switch( tag->value_type )
		{
		case MEXIF_VALUE_BYTE   :
		case MEXIF_VALUE_UINT16 :
		case MEXIF_VALUE_UINT32 :
			val = mexif_tag_value_uint_index( tag, i );
			if ( val & 0xff000000 )
				str.AppendFormat( TEXT("0x%08x (%u)"), val, val );
			else
				str.AppendFormat( TEXT("%u"), val );
			break;

		case MEXIF_VALUE_SINT8  :
		case MEXIF_VALUE_SINT16 :
		case MEXIF_VALUE_SINT32 :
			str.AppendFormat( TEXT("%u"), mexif_tag_value_int_index( tag, i ) );
			break;

		case MEXIF_VALUE_RATIONAL :
		case MEXIF_VALUE_SRATIONAL :
			str.AppendFormat( TEXT("%f"), mexif_tag_value_double_index( tag, i ) );
			break;

		case MEXIF_VALUE_UNDEFINED :
			str.AppendFormat( TEXT("%02x"), tag->value_pointer[i] );
			break;
		}

		if ( maxCount > 1 && i+1 != maxCount )
			str.Append( TEXT(", ") );
	}

	if ( maxCount > 1 && tag->value_count > maxCount )
		str.Append( TEXT(", ...") );
	if ( maxCount > 1  )
		str.Append( TEXT(" }") );
}

bool FormatTagValue_DateTime( CString& str, mexif_tag_t* tag ) 
{
	mexif_time_t t;
	if ( MEXIF_RET_OK == mexif_tag_value_time( tag, &t ) )
	{
		str.AppendFormat( TEXT("%04d.%02d.%02d %02d:%02d:%02d"), 
			t.year, t.month, t.date,
			t.hour, t.minute, t.second
			);
		return true;
	}
	else
		return false;
}

